class Compile {
    constructor(el, vm) {
        this.vm = vm
        if (this.isElementNode(el)) {
            this.el = el
        } else {
            this.el = document.querySelector(el)
        }
        if (this.el) {
            // 这里应该就是 vue 用 vdom 实现的地方
            // 使用 vdom 的一个原因在于 fragment 不能在 node 环境中使用
            this.fragment = this.node2Fragment(this.el)
            this.compileElement(this.fragment)
            this.el.appendChild(this.fragment)
        }
    }

    node2Fragment(el) {
        let fragment = document.createDocumentFragment()
        let child
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    }

    compileElement(el) {
        let childNodes = el.childNodes;
        [].slice.call(childNodes).forEach(node => {
            let text = node.textContent
            let reg = /\{\{(.*)\}\}/
            if (this.isElementNode(node)) {
                this.compile(node)
            } else if (this.isTextNode(node) && reg.test(text)) {
                this.compileText(node, RegExp.$1)
            }

            if (node.childNodes && node.childNodes.length) {
                this.compileElement(node)
            }
        })
    }

    compile(node) {
        let nodeAttrs = node.attributes;
        [].slice.call(nodeAttrs).forEach(attr => {
            let exp = attr.value
            let attrName = attr.name
            let dir = attrName.substring(2)

            if (this.isEventDirective(dir)) {
                compileUtil.eventHandler(node, this.vm, exp, dir)
            } else {
                if (compileUtil[dir]) {
                    compileUtil[dir](node, this.vm, exp)
                }
            }

            node.removeAttribute(attrName)
        })
    }

    compileText(node, exp) {
        compileUtil.text(node, this.vm, exp)
    }

    isDirective(attr) {
        return attr.indexOf('v-') === 0
    }

    isEventDirective(dir) {
        return dir.indexOf('on') === 0
    }

    isElementNode(node) {
        return node.nodeType === 1
    }

    isTextNode(node) {
        return node.nodeType === 3
    }
}

// 指令
let compileUtil = {
    bind(node, vm, exp, dir) {
        let updaterFn = updater[dir + 'Updater']
        if (updaterFn) {
            updaterFn(node, this.getVMVal(vm, exp))
        }
        new Watcher(vm, exp, (value, oldValue) => {
            if (updaterFn) {
                updaterFn(node, this.getVMVal(vm, exp))
            }
        })
    },

    text(node, vm, exp) {
        this.bind(node, vm, exp, 'text')
    },

    html(node, vm, exp) {
        this.bind(node, vm, exp, 'html')
    },

    eventHandler(node, vm, exp, dir) {
        let eventType = dir.split(':')[1]
        let fn
        // 如果传入的有回调
        if (vm.options.methods) {
            fn = vm.options.methods[exp]
        }
        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },

    model(node, vm, exp) {
        this.bind(node, vm, exp, 'model')
        let val = this.getVMVal(vm, exp)
        node.addEventListener('input', e => {
            let newValue = e.target.value
            if (val === newValue) {
                return
            }
            // 在这里修改 mvvm 对象中相应的值
            // exp 是 key
            this.setVMVal(vm, exp, newValue)
            val = newValue
        })
    },

    class(node, vm, exp) {
        this.bind(node, vm, exp, 'class')
    },

    getVMVal(vm, exp) {
        let val = vm
        exp = exp.split('.')
        exp.forEach((k) => {
            val = val[k]
        })
        return val
    },

    setVMVal(vm, exp, value) {
        let val = vm
        exp = exp.split('.')
        exp.forEach((k, i) => {
            if (i < exp.length - 1) {
                val = val[k]
            } else {
                val[k] = value
            }
        })
    }
}

let updater = {
    textUpdater(node, value) {
        if (typeof value === 'undefined') {
            node.textContent = ''
        } else {
            node.textContent = value
        }
    },

    htmlUpdater(node, value) {
        if (typeof value === 'undefined') {
            node.innerHTML = ''
        } else {
            node.innerHTML = value
        }
    },

    classUpdater(node, value, oldValue) {
        let className = node.className
        className = className.replace(oldValue, '').replace(/\s$/, '')
        let space
        if (className && String(value)) {
            space = ' '
        } else {
            space = ''
        }
        node.className = className + space + value
    },

    modelUpdater: function (node, value, oldValue) {
        node.value = typeof value == 'undefined' ? '' : value;
    }
}
